#include "sedona.h"

#include <sys/mman.h>
#include <fcntl.h>

#undef PAGE_SIZE
#undef PAGE_MASK
/**
  *		GPDCON	0x56000030
  *		GPDDAT	0x56000034 = 0x56000030 + 4
  *		GPDUP     0x56000038 = 0x56000030 + 8
  */

#define PAGE_SHIFT		12
#define PAGE_SIZE  		(1<<PAGE_SHIFT)		/* 4096byte Per Mapped Memory page */
#define PAGE_MASK 		(~(PAGE_SIZE-1) )

#define GPIO_EndAddr	0x560000DC
#define GPIO_StartAddr	0x56000000

#define CLKREG_StartAddr	0x4C000000 
#define CLKREG_EndAddr	0x4C000018 
#define CLKCON 		0x0C

#define INPUTGPIO		0
#define OUTPUTGPIO		1

#define REG(Addr)	(*(volatile unsigned int*) (Addr))

static char* GPIOConStatus[4] = {"INPUT", "OUTPUT", "SpecialUse", "Reserved"};
static char* GPIODataStatus[2] = {"LOW", "HIGH"};
static char* GPIOPullupStatus[2] = {"Enabled", "Disabled"};
int g_fd=-1, g_opt = -1, g_Bank_num=-1, g_Addr_offset=-1, g_MaskBit=-1, g_Function=-1, g_to=-1,g_pullup=-1;
void* g_GpioBase = NULL;
void* g_CLKBASE = NULL;


/* enable PCLK(50Mhz) to GPIO Block */
static void s3c2440_clkcon_init(void*  REG_CLK)
{
	REG((unsigned int)REG_CLK + CLKCON) |=0x2000;	
}

static void s3c2440_gpio_cfgpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int Function)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	
	if(Addr_offset>0)
	{
		Function &= 3;
		tmp &= ~(3<<MaskBit);		/*used for GPIO Bank B-J, which use the 2 bits Mask*/
	}
	else	
	{
		Function &= 1;
		tmp &= ~(1<<MaskBit);		/*used for GPIO Bank A, which use the 1 bit Mask*/
	}
	
	tmp|= (Function<<MaskBit);	
	REG((unsigned int)Regaddr +Addr_offset)=tmp;
}

static unsigned int s3c2440_gpio_getcfg(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);		
	tmp >>= MaskBit;
	
	if(Addr_offset>0x0c)
		tmp &= 3;	/*used for GPIO Bank B-J, which use the 2 bits Mask*/
	else
		tmp &= 1;	/*used for GPIO Bank A, which use the 1 bit Mask*/

	return tmp;
}

static void s3c2440_gpio_setpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int to)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	tmp &= ~(1<<MaskBit);	
	tmp |= (!!to<<MaskBit);
	REG((unsigned int)Regaddr +Addr_offset)=tmp;	
}

static unsigned int s3c2440_gpio_getpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);
	tmp &= (1<<MaskBit);	
	return tmp?1:0;
}

static void s3c2440_gpio_pullup(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int to)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	tmp &= ~(1<<MaskBit);
	tmp |= !!to << MaskBit;
	REG((unsigned int)Regaddr +Addr_offset)=tmp;
}

static unsigned int s3c2440_gpio_getpull(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);
	tmp &= (1<<MaskBit);	
	return tmp?1:0;
}

static int open_mem()
{
	int fd =-1;
	
	fd = open("/dev/mem", O_RDWR | O_SYNC);
	if(fd<0)
	{
		fprintf(stderr, "open /dev/mem error\n");
		exit(1);
	}
	
       return fd;
}

static void setmap()
{
	g_CLKBASE = mmap(NULL, (CLKREG_EndAddr-CLKREG_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,g_fd, CLKREG_StartAddr & PAGE_MASK);
	if(g_CLKBASE == NULL || g_CLKBASE == MAP_FAILED)
	{
		fprintf(stderr, "[Error]: S3C2440 CLKCON base mmap error\n");
		
	}
	//fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X bytes\n",CLKREG_StartAddr,CLKBASE ,(CLKREG_EndAddr-CLKREG_StartAddr));

	g_GpioBase = mmap(NULL, (GPIO_EndAddr-GPIO_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,g_fd, GPIO_StartAddr & PAGE_MASK);
	if(g_GpioBase == NULL || g_GpioBase == MAP_FAILED)
	{
		fprintf(stderr, "S3C2440 gpio base mmap error\n");
	}
}

static int init()
{
	int i,j,k;
	int32_t bank_num;

	g_fd = open_mem();
	setmap();
	s3c2440_clkcon_init(g_CLKBASE);
}

static void unmap()
{
	munmap(g_GpioBase,(GPIO_EndAddr-GPIO_StartAddr)); 
	munmap(g_CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr));
	g_GpioBase = NULL;
	g_CLKBASE = NULL;
}

static void closedown()
{
	unmap();
	close(g_fd);
	exit(0);
}

void  failed()
{
	g_Bank_num = -1;
	g_Addr_offset = -1;
	g_MaskBit = -1;
	g_Function = -1,
	g_to = -1;

	munmap(g_GpioBase,(GPIO_EndAddr-GPIO_StartAddr)); 
	munmap(g_CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr));
	close(g_fd);
	g_GpioBase = NULL;
	g_CLKBASE = NULL;
	exit(1);
}

/**
  *     bank_num GPA~GPJ totally 8 banks. note that there are no GPI port group, mask_bit represents the real pin num, function
  *     get values from GPIOConStatus, that 0 is input, 1 is output
  *     value get value from GPIODataStatus
  */
static void s3c2440_gpio_operation(int bank_num, int mask_bit, int function, int value)
{        
	//locate the bank number -- the port A to J
	g_Bank_num = bank_num;				
	if ( g_Bank_num < 0 || g_Bank_num > 8) {						
		fprintf(stderr, "[Error]: Please input correct Bank index num, the Bank index num should be 0--8 !\n");
		failed();
	}
	
	g_Addr_offset = g_Bank_num * 0x10;
	if (g_Addr_offset == 0x80)  g_Addr_offset += 0x50;

	//locate the pin of the bank
	g_MaskBit = mask_bit;
	if ( g_MaskBit < 0 || g_MaskBit > 25) {
		fprintf(stderr, "[Error]: Please input correct GPIO index num, the GPIO index num should be 0--25(max) !\n");
		failed();
	}

	//config the gpio_config_reg
	g_Function = function;
   	if ( g_Function < 0 || g_Function > 3) {
		fprintf(stderr, "[Error]: Please input correct 'Config_reg' value, the value should be 0x00--0x03 !\n");
		failed();
	}
						
  	//write to the gpio register
       g_to = value;					
       if( g_to < 0 || g_to > 1){					
          fprintf(stderr, "[Error]: Please input correct 'Data_reg' value, the value should be 0x00--0x01 !\n");
          failed();
       }										
						
       s3c2440_gpio_setpin(g_GpioBase, g_Addr_offset+4, g_MaskBit, g_to); //set GPxData
      // fprintf(stdout,"...Writing the 'Data_reg' to %s...\n",GPIODataStatus[g_to]);						
}

void gpioInputConfig(int bank, int mask)
{
	g_Addr_offset = bank * 0x10;
	if(g_Addr_offset == 0x80) {
		g_Addr_offset += 0x50;
	}

	s3c2440_gpio_cfgpin(g_GpioBase, g_Addr_offset, mask*2, INPUTGPIO); 		// config as input gpio
	s3c2440_gpio_pullup(g_GpioBase, g_Addr_offset + 8, mask, 0);	// enable pull-up function

}

void gpioOutputConfig(int bank, int mask, int initValue)
{
	g_Addr_offset = bank * 0x10;
	if(g_Addr_offset == 0x80) {
		g_Addr_offset += 0x50;
	}

	s3c2440_gpio_operation(bank, mask, OUTPUTGPIO, initValue);
	s3c2440_gpio_cfgpin(g_GpioBase, g_Addr_offset, mask*2, OUTPUTGPIO); 		// config as output gpio
	s3c2440_gpio_pullup(g_GpioBase, g_Addr_offset + 8, mask, 0);	// enable pull-up function

}



Cell gpio_GpioControl_doInit(SedonaVM* vm, Cell* params)
{
	init();
}

Cell gpio_GpioControl_doClose(SedonaVM* vm, Cell* params)
{
	closedown();
}

Cell gpio_GpioControl_gpioOutputConfig(SedonaVM * vm,Cell * params)
{
	 int32_t bank_num  = params[1].ival;
	 int32_t mask_bit  = params[2].ival;
	 int32_t initValue  = params[3].ival;

	gpioOutputConfig(bank_num, mask_bit, initValue);
}

Cell gpio_GpioControl_gpioInputConfig(SedonaVM * vm,Cell * params)
{
	 int32_t bank_num  = params[1].ival;
	 int32_t mask_bit  = params[2].ival;

	gpioInputConfig(bank_num, mask_bit);
}



Cell gpio_GpioControl_gpioControl(SedonaVM* vm, Cell* params)
{
	 int32_t bank_num  = params[1].ival;
	 int32_t mask_bit  = params[2].ival;
	 int32_t function  = params[3].ival;
	 int32_t value     = params[4].ival;

   	s3c2440_gpio_operation(bank_num,mask_bit,function,value); 
}

  
Cell gpio_GpioControl_gpioInput (SedonaVM* vm, Cell* params)
{
        Cell ret;
        int inVal;
        
	 int32_t bank_num  = params[1].ival;
	 int32_t mask_bit  = params[2].ival;

        g_Addr_offset = bank_num * 0x10;
        if(g_Addr_offset == 0x80) {
            g_Addr_offset += 0x50;
	 }
         
   	inVal = s3c2440_gpio_getpin(g_GpioBase, g_Addr_offset + 4, mask_bit);  //get GPxData

       ret.ival = inVal;

       return ret;
}

